Skip to content

feat: the truth gate — the DJ stops lying (T329–T335) - #609

Merged
genwave-radio merged 8 commits into
mainfrom
feat/truth-gate
Aug 21, 2026
Merged

feat: the truth gate — the DJ stops lying (T329–T335)#609
genwave-radio merged 8 commits into
mainfrom
feat/truth-gate

Conversation

@genwave-radio

Copy link
Copy Markdown
Collaborator

The gate cluster of the truth-lane epic (SPEC F138–F139, designed 2026-08-20), built under /build-loop with opus review gates — 17 review rounds across seven tasks, every finding closed mutation-red. Builds on #605 (the mechanics lane).

Closes #434. Closes #365.

What ships

F138 — the truth gate. A pure CopyClaims checker (present-frame collocation semantics — anticipation, recall, and displaced references never violate; both aired exhibits do, in their real curly-apostrophe forms) wired at the one LlmCopyWriter seam: a violating reply gets exactly ONE re-ask naming the claim, sharing the first call's timeout budget, then the kind's own floor (context skips per F107.6; lead-ins land on a template whose fixed prose names no day). The F138.5 guard line rides every patter prompt including banter. Crosstalk gains five mechanical verifiable checks (integer + decimal FM, AM frequencies vs clock times, call signs, weather, dates, whole-script clock) through F127.4's fail-closed discard — fictional lore passes untouched. Crosstalk:DurationTargetSeconds defaults to the ratified 50s.

F139 — red explains itself. Eight typed causes (MalformedResponse added at review — a mistagged 12-line script is not an "empty completion") stamped where calls resolve, fed through one required LlmCallRecorder into the F73 ring + 24h (cause, model, kind) counters. GET /api/llm-calls returns {calls, causeSummary}; the dashboard's red LLM tile names its dominant recent cause and model ("Red: 6 timeouts in the last 24h, gemma3:12b"); the llm-calls feed gains a cause chip. Success is never a candidate, ties are deterministic — both inversion-pinned.

The wire proof

Story350_TruthLaneEndToEnd drives the production DI graph end to end, and the live smoke ran through the deployed preview endpoint on a real Kestrel + Postgres + scripted stub LLM: one poisoned reply re-asked once and the clean text returned; both-poisoned exhausted the ladder to an honest 502 — the truth gate rejected the re-ask too (wrong-day claim: Saturday); the cause summary counted every reject against the stub model.

Spec honesty along the way

Four SPEC/STORIES amendments were forced by review evidence (the F135.5 precedent): F138.2/.3 narrowed to present-frame semantics after live probes showed bare-token matching rejecting half of realistic patter; F139.1 gained its eighth value; the counters' letter corrected to the honest 24–25h band; AC6 reconciled to the F107.6 skip floor.

Verification

Final full run: 4,418 .NET tests + 1,117 jest, zero failures, zero warnings, fitness laws green. 30+ review mutations across the epic, each now red against a named fact. gh-#438 closes after Dean's bench week (T336 — reading the per-model gate counters on the 4090 and recording the model floor in HARDWARE.md).

…as amended)

Pure static checker beside SpeechText (F68.6 posture): digit-run /
weekday / condition-word extraction with whole-token fact support
(decimal-prefix allowance kept deliberately), and present-frame clock
claims — weekday under a closed marker set, daypart under greeting/
copula, overlapping daypart windows, track-title exemption, both
apostrophe forms. Vocabulary v2 (clear dropped — its non-weather sense
dominates DJ prose). Violations are closed-vocabulary data safe for
re-ask prompt interpolation (reviewer-verified fence-forging-free).
When in doubt, PASSES: anticipation, recall, and displaced references
are the bread of DJ patter and never violate.

Review: 3 opus rounds + 1 confirm — round 1's probes showed bare-token
matching rejecting 5/10 realistic patter lines (SPEC F138.2/.3 amended
to present-frame semantics, the F135.5 precedent), the substring digit
rule unfalsifiable against date-bearing facts, and clear firing on
'let me be clear'; round 3 caught U+2019 disabling the it's marker —
the aired-defect shape passing clean. Both gh-#434 and gh-#438
exhibits pinned in their real forms. Wiring lands in T331/T332.
…3 as amended)

Eight typed causes (MalformedResponse added at review — a 12-line
mistagged script is not an 'empty completion', and the model-floor
signal depends on the distinction) stamped where calls resolve, decided
once at the source: CleanCopy's fit/over-length/empty split, the
timeout-vs-connection classifier, crosstalk's parser rejects, and the
worker's window cancellation (reusing T328's GenerationAttempted
signal). LlmCallRecorder (required dep) is the one seam feeding both
the F73 ring and the new (cause, model, kind) counters — hourly-bucket
24-25h band, TimeProvider-driven, nothing persists (F73.3 stands).
Ring entry grows Cause + Model additively; Story196's ctor-arity proof
untouched. Rides along (ruled in at review): Story317's date bomb
defused (hardcoded 2026-08-20 red every run from today); the crosstalk
test harness no longer leaks a temp dir per build (69k dirs had
inode-choked tmpfs, silently redding 160+ unrelated facts).

Review: 3 opus rounds — the malformed-shape fold rejected (SPEC F139.1
amended), the optional-counter-param pattern rejected mutation-proven
(deleting every counter feed left the suite green; the recorder makes
it structurally undeletable), the 24h retention letter corrected to
the honest band. Surface lands in T334; TruthGateReject stamps in T331.
CopyClaims.CheckFacts wired at the one completion seam for context
segments with a non-empty fact block: a violating first reply records
TruthGateReject and triggers exactly ONE re-ask naming the claim
('Your last reply stated "6" and "sunshine" but the facts above never
said that…' — comma-free, fence-safe by T329's closed-vocabulary
guarantee), sharing the first call's timeout clock so the worst-case
feeder hold never grows; a still-violating re-ask lands on the
F107.6 floor (the ContextSegment skip) via a new closed-hierarchy
TruthGateRejected arm whose WARN names the real cause — never the
MaxCopyChars lever. Both calls get honest ring entries (own start, own
prompt, own cause). The F138.5 guard line rides EVERY patter prompt
('It is {weekday} {daypart}. Never name another day or time of day.').
Admin previews are structurally ungated (no ContextFacts by
construction — documented). The ladder is an extracted choreography
T332's clock check composes into.

Review: 3 opus rounds — r1's mutations caught the budget fact vacuous
(a fresh re-ask clock survived 729 facts), the guard line pinned on one
kind only, the exhausted-ladder WARN lying about its lever, and
unpinned ring attribution; all closed mutation-red. gh-#434's exhibit
airs clean end-to-end: poisoned reply → re-ask → honest second reply.
CheckTruthGate composes facts and clock into ONE ladder run — the
facts half scoped by its own null-guard to context segments, the clock
half unconditional on every LLM-authored kind including previews (the
factless-context exemption was deleted at review: reachable via the
preview endpoint, and a missing fact block excuses nothing about a
wrong-day claim). Rejection wording is honest per violation class
(unsupported claim / wrong-day claim / wrong-time-of-day claim) in
both the WARN and the re-ask, keyed on one discriminator
(ClaimViolation.IsClockClaim). Floors verified per kind: context/
sign-off/sign-on drop (F107.6/F92 skip), lead-in/back-announce land on
the template whose fixed prose names no day. Preview failures name the
truth gate, never 'empty or over-length' (the wrong-lever class, kept
out this time). gh-#438's exhibit airs clean end-to-end: 'Saturday
morning… Tonight' at Sunday 11:50 re-asks once and the honest reply airs.

Review: 2 opus rounds + confirm — r1 caught the reachable exemption,
the preview wrong-lever message, zero preview coverage, and a
surviving log-wording mutant; all closed mutation-red. Tts 739/0.
…as amended)

The crosstalk validator's fail-closed discard gains five mechanical
truth checks (after shape validation — structure passed, content
lied): frequency shapes incl. integer FM dial positions (review
caught 'Radio 101 FM' airing under a decimal-only rule; '9 AM' the
clock time still passes), K/W call signs, weather condition words
(the shared CopyClaims matcher — no second list), date shapes, and
whole-script clock claims via CheckClock at the request's own
instant. All stamp TruthGateReject — the honest per-model signal
for 'this model invents broadcast facts'. The narrow anti-fabrication
clause plus the F138.5 clock guard line now ride the banter system
prompt (review: banter was the one kind clock-checked with no ladder
AND no prompt rule — the model deserved to be told). Fictional lore
passes untouched: recurring characters and running gags validate
clean by construction. Crosstalk:DurationTargetSeconds default 25→50
(the ratified live-convergence figure), coherent through the F123.1
cap derivation and the settings help text.

Review: 2 opus rounds + confirm — integer-FM hole, the missing guard
line, and a stale 'Defaults to 25' on the deployed settings page;
all closed probe-proven. Tts 751/0.
…9.2/.4)

GET /api/llm-calls returns {calls, causeSummary}: per-call cause+model
plus the unscoped 24h (cause, model, kind) counters — the crosstalk
lane visible without SSH. /api/status's llm block gains the dominant
failure triple via LlmCallCauseCounters.DominantFailure(Copy) —
honestly scoped to the one lane whose verdict the tile reflects
(LlmCopyStatusHolder has exactly one writer), riding the existing
status poll (no new poller — the gh-#558 lesson). The red tile renders
'Red: 6 timeouts in the last 24h, gemma3:12b' beneath the failure
line; green tiles never explain a fault that didn't cause them. The
llm-calls feed gains a cause chip. Success is never a candidate
(pinned with Success in the outright majority), ties break
deterministically (enum order then model — direction inversion-pinned),
and a Success-only day yields the null triple on both halves.

Review: 2 opus rounds + confirm — both invariants were mutation
survivors ('Red: 200 success' was live-reachable); closed spec-side.
Shared LlmCompletionsStub extracted to Support/ for T335. gh-#365's
acceptance: no SSH, no Loki, no darts.
…F139 build)

Story350_TruthLaneEndToEnd drives the production DI graph: a poisoned
context render re-asks once and airs the clean reply with both causes
on GET /api/llm-calls; a double-poisoned lead-in degrades to the
template with /api/status naming truthgatereject as the dominant
cause; the crosstalk lane's counters visible on the unscoped summary;
the F138.5 guard line asserted on every captured wire prompt. Three
discriminating review mutations across gate, endpoint projection, and
tile scoping. LlmCompletionsStub grows call-sequenced replies +
request capture, additively.

Live smoke (real Kestrel + real Postgres + scripted stub LLM through
the deployed preview endpoint): one poisoned reply re-asked once and
the clean text returned; both-poisoned exhausted the ladder to HTTP
502 'the truth gate rejected the re-ask too (wrong-day claim:
Saturday)' — the honest lever, live; causeSummary counted 3
truth-gate rejects for the stub model. Review: 1 opus round, PASS.
@genwave-radio
genwave-radio merged commit 257c801 into main Aug 21, 2026
11 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 21, 2026
@genwave-radio
genwave-radio deleted the feat/truth-gate branch August 21, 2026 12:17
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

1 participant